/**
 * 
 */
package io.gitee.falllee.dao.core;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.calcite.linq4j.Enumerable;
import org.apache.calcite.linq4j.Linq4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import io.gitee.falllee.dao.annotation.Column;
import io.gitee.falllee.dao.annotation.Table;
import io.gitee.falllee.dao.model.DaoConst;
import io.gitee.falllee.dao.model.DaoException;
import io.gitee.falllee.dao.persistence.DataMapper;
import io.gitee.falllee.dao.util.CommonUtils;
import io.gitee.falllee.dao.util.DaoUtils;
import io.gitee.falllee.dao.util.FilterPlus;

/**
 * @author batty
 * @date 2022-06-27
 *
 */
public class UpdateBuilder<T> extends HeaderBuilder {
    
    private Class<T> clazz;

    private Dao dao;

    private DataMapper dataMapper;
    
    private CacheBuilder<T> cacheBuilder;
    
    private MessageBuilder<T> messageBuilder;

    private Boolean ignoreNullData = true;
    
    private String updateFields;
    
    UpdateBuilder(Dao parent, Class<T> clazz) {
        super(parent.getDaoConfig());
        this.clazz = clazz;
        this.dao = parent;
        this.dataMapper = parent.getDataMapper();
        this.cacheBuilder = parent.cacheBuilder(clazz);
        this.messageBuilder = parent.messageBuilder(clazz);
    }

    public UpdateBuilder<T> header(String key, String value) {
        if (headerParamsMap == null) {
            headerParamsMap = new HashMap<>(16);
        }
        headerParamsMap.put(key, value);
        return this;
    }
    
    public UpdateFilterBuilder<T> filter(FilterPlus filter) {
        return new UpdateFilterBuilder<>(this, daoConfig, filter);
    }
    
    public UpdateBuilder<T> allowUpdateNullData() {
        this.ignoreNullData = false;
        return this;
    }
    
    public UpdateBuilder<T> updateFields(String updateFields) {
        this.updateFields = updateFields;
        return this;
    }
    
    public Boolean update(T t) {
        if (t == null) {
            return false;
        }
        Table table = DaoUtils.getTable(clazz);
        Map<String, Object> map = new HashMap<>(16);
        map.put(DaoConst.DB_TYPE, daoConfig.getDbType());
        boolean blSyncTable = table.syncTable();
        String syncDateField = table.syncDateField();
        Integer syncDatePrecision = table.syncDatePrecision().getPrecision();
        Object keyValue = setUpdateFieldAndValue(t, map, blSyncTable, syncDateField);
        String tableName = table.value();
        map.put(DaoConst.TABLE_NAME, tableName);
        if (!blSyncTable) {
            setEnableFlag(map, false);
            setUpdateInfo(map);
        } else {
            setSyncDate(map, syncDateField, syncDatePrecision);
        }
        Integer res = dataMapper.update(map);
        if (res > 0) {
            // 如果启用缓存，删除缓存数据
            cacheBuilder.clearCache(keyValue);
            // 发送Kafka消息
            messageBuilder.sendMessage(keyValue, DaoConst.UPDATE);
        }
        return res >= 0;
    }

    public Boolean updateBatch(List<T> list) {
        List<T> tempList = Linq4j.asEnumerable(list).where(p -> !ObjectUtils.isEmpty(p)).distinct().toList();
        if (CollectionUtils.isEmpty(tempList)) {
            return false;
        }
        Class<? extends Object> clazz = tempList.get(0).getClass();
        Table table = DaoUtils.getTable(clazz);
        String tableName = table.value();
        String syncDateField = table.syncDateField();
        Method[] methods = clazz.getMethods();
        Map<String, Object> genMap = new HashMap<>(16);
        genMap.put(DaoConst.DB_TYPE, daoConfig.getDbType());
        Method keyGetMethod = DaoUtils.getKeyGetMethod(clazz);
        genMap.put(DaoConst.TABLE_NAME, tableName);
        Column keyColumn = keyGetMethod.getAnnotation(Column.class);
        genMap.put("id", keyColumn.value());
        boolean blSyncTable = table.syncTable();
        if (!blSyncTable) {
            setEnableFlag(genMap, false);
            setUpdateInfo(genMap);
        } else {
            Integer syncDatePrecision = table.syncDatePrecision().getPrecision();
            setSyncDate(genMap, syncDateField, syncDatePrecision);
        }
        // 组件级字段
        List<String> configFields = getComponentConfigFields(blSyncTable, syncDateField);
        String[] updateFieldArr = new String[0];
        if (StringUtils.hasLength(updateFields)) {
            updateFieldArr = updateFields.toLowerCase().split(DaoConst.COMMA);
        }
        Map<String, String> paramList = getUpdateParamList(methods, configFields, updateFieldArr);
        int total = tempList.size();
        int batchSize = table.updateBatchMaxSize();
        // 执行次数
        int num = total / batchSize;
        // 最后一次剩余记录数
        int left = total % batchSize;
        if (left > 0) {
            num += 1;
        }
        List<Object> keyValueList = new ArrayList<>();
        Integer res = 0;
        for (int i = 0; i < num; i++) {
            List<T> subList = CommonUtils.getSubList(tempList, batchSize, i);
            // 主键值的集合
            res += updateBatchSubList(ignoreNullData, clazz, genMap, updateFieldArr, paramList, keyValueList, subList);
        }
        if (res > 0) {
            // 清除实体缓存
            cacheBuilder.clearCache(keyValueList);
            // 发送Kafka消息
            messageBuilder.sendMessage(keyValueList, DaoConst.UPDATE_LIST);
        }
        return res >= 0;
    }

    private Object setUpdateFieldAndValue(T t, Map<String, Object> map, boolean blSyncTable, String syncDateField) {
        Method[] methods = clazz.getMethods();
        String keyField = DaoUtils.getKeyField(clazz);
        Object keyValue = DaoUtils.getKeyValue(t);
        if (ObjectUtils.isEmpty(keyValue)) {
            throw new DaoException("keyword value must be not null");
        }
        String fieldValue = "";
        // 主键作为更新条件
        String filter = getParamExpression(keyField);
        StringBuilder sbFieldValue = new StringBuilder();
        // 业务自定义固定字段
        List<String> fixFieldList = listFixField();
        // 组件级固定字段
        List<String> configFields = getComponentConfigFields(blSyncTable, syncDateField);
        String[] updateFieldArr = new String[0];
        if (StringUtils.hasLength(updateFields)) {
            updateFieldArr = updateFields.toLowerCase().split(DaoConst.COMMA);
        }
        List<String> updateFieldList = Arrays.asList(updateFieldArr);
        Enumerable<Method> erMethod = getValidateMethods(methods, configFields);
        for (Method method : erMethod) {
            Column col = method.getAnnotation(Column.class);
            String columnName = getColumnValue(col);
            Object objVal = DaoUtils.getValue(t, method);
            String colQuotes = getQuoteField(columnName);
            // 如果指定了更新字段，无论是否空值均按指定字段更新
            // 如果未指定更新字段，空值不更新
            // 业务自定义固定字段不更新
            boolean notUpdate = col == null
                    || !ObjectUtils.isEmpty(updateFieldArr) && !updateFieldList.contains(columnName.toLowerCase())
                    || ObjectUtils.isEmpty(updateFieldArr) && ignoreNullData && ObjectUtils.isEmpty(objVal)
                    || fixFieldList.contains(columnName.toLowerCase()) || sbFieldValue.toString().contains(colQuotes);
            if (notUpdate) {
                continue;
            }
            // 组装set语句，非主键字段
            if (!col.key()) {
                sbFieldValue.append(getParamExpression(columnName));
                sbFieldValue.append(DaoConst.COMMA);
            }
            // 赋值
            map.put(columnName, objVal);
        }
        if (sbFieldValue.length() > 0) {
            fieldValue = sbFieldValue.substring(0, sbFieldValue.length() - 1);
        } else {
            throw new DaoException(DaoConst.FIELD_CAN_NOT_BE_EMPTY);
        }
        map.put("fieldValue", fieldValue);
        map.put(DaoConst.FILTER, filter);
        map.computeIfAbsent(keyField, p-> keyValue);
        return keyValue;
    }

    private Integer updateBatchSubList(Boolean ignoreNullData, Class<? extends Object> clazz,
        Map<String, Object> genMap, String[] updateFieldArr, Map<String, String> paramList, List<Object> keyValueList,
        List<T> subList) {
        Integer res;
        List<Object> subKeyValueList = new ArrayList<>();
        for (T t : subList) {
            Object keyValue = DaoUtils.getKeyValue(t);
            subKeyValueList.add(keyValue);
        }
        keyValueList.addAll(subKeyValueList);
        Map<String, List<Map<Object, Object>>> columnCaseMap = new HashMap<>(16);
        Iterator<Entry<String, String>> iterator = paramList.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<String, String> entry = iterator.next();
            String columnName = entry.getKey();
            String getMethodName = entry.getValue();
            List<Map<Object, Object>> caseList = getCaseValue(ignoreNullData, clazz, updateFieldArr, subList,
                getMethodName);
            if (!CollectionUtils.isEmpty(caseList)) {
                columnCaseMap.put(getQuoteField(columnName), caseList);
            }
        }
        res = dataMapper.updateBatch(genMap, subKeyValueList, columnCaseMap);
        return res;
    }

    private Map<String, String> getUpdateParamList(Method[] methods, List<String> configFields,
        String[] updateFieldArr) {
        // 业务自定义固定字段
        List<String> fixFieldList = listFixField();
        List<String> updateFieldList = Arrays.asList(updateFieldArr);
        Map<String, String> paramList = new HashMap<>(16);
        Enumerable<Method> erMethod = getValidateMethods(methods, configFields);
        for (Method method : erMethod) {
            Column col = method.getAnnotation(Column.class);
            String columnName = getColumnValue(col);
            // 主键或非指定字段排除或业务自定义固定字段排除
            if (col.key() || !ObjectUtils.isEmpty(updateFieldArr) && !updateFieldList.contains(columnName.toLowerCase())
                    || fixFieldList.contains(columnName.toLowerCase())) {
                continue;
            }
            paramList.put(columnName, method.getName());
        }
        if (paramList.isEmpty()) {
            throw new DaoException(DaoConst.FIELD_CAN_NOT_BE_EMPTY);
        }
        return paramList;
    }

    private List<Map<Object, Object>> getCaseValue(Boolean ignoreNullData, Class<? extends Object> clazz,
        String[] updateFieldArr, List<T> subList, String getMethodName) {
        List<Map<Object, Object>> caseList = new ArrayList<>();
        for (T t : subList) {
            Object keyValue = DaoUtils.getKeyValue(t);
            Method getMethod = DaoUtils.getMethod(clazz, getMethodName);
            Object objVal = DaoUtils.getValue(t, getMethod);
            // 主键必须非空；如果指定了更新字段，无论空值均需要更新
            boolean notUpdate = ObjectUtils.isEmpty(keyValue)
                    || ObjectUtils.isEmpty(updateFieldArr) && ObjectUtils.isEmpty(objVal) && ignoreNullData;
            if (notUpdate) {
                continue;
            }
            Map<Object, Object> caseValues = new HashMap<>(16);
            caseValues.put(keyValue, objVal);
            caseList.add(caseValues);
        }
        return caseList;
    }

    /**
     * 根据Map的键值对更新（更新字段空值推荐此方法）
     * 
     * @param headerParamsMap 请求头列表
     * @param fieldValueMap   更新字段的键值对（根据条件更新，无需传主键的键值对）
     * @param filter          条件
     * @param clazz           实体类型
     * @return 是否成功
     */
    Boolean update(Map<String, Object> fieldValueMap, FilterPlus filterPlus) {
        if (ObjectUtils.isEmpty(fieldValueMap)) {
            return false;
        }
        String paramFilter = filterPlus.getParamFilter();
        if (!StringUtils.hasLength(paramFilter)) {
            throw new DaoException("filter must not be empty");
        }
        Table table = DaoUtils.getTable(clazz);
        String tableName = table.value();
        Map<String, Object> map = filterPlus.getParamMap();
        map.put(DaoConst.DB_TYPE, daoConfig.getDbType());
        String keyField = DaoUtils.getKeyField(clazz);
        map.put(DaoConst.TABLE_NAME, tableName);
        map.put(DaoConst.FILTER, paramFilter);
        setUpdateKeyValueMap(fieldValueMap, keyField, map);
        setUpdateInfo(table, map);
        // 先查待更新的数量，分页进行更新
        long count = dataMapper.selectCount(map);
        long num = count / DaoConst.SELECT_MAX_SIZE;
        long left = count % DaoConst.SELECT_MAX_SIZE;
        num = left > 0 ? num + 1 : num;
        long rows = DaoConst.SELECT_MAX_SIZE;
        Integer res = 0;
        for (long i = 0; i < num; i++) {
            long end = i == num - 1 ? count : (i + 1) * rows;
            map.put(DaoConst.FILTER, paramFilter);
            List<Object> keyList = dao.getKeyList(map, keyField, i * rows, rows, end);
            String filter = getKeyListFilter(keyList, keyField, map);
            map.put(DaoConst.FILTER, filter);
            res += dataMapper.update(map);
            if (res > 0) {
                // 如果启用缓存，删除缓存数据
                cacheBuilder.clearCache(keyList);
                // 发送Kafka消息
                messageBuilder.sendMessage(keyList, DaoConst.UPDATE_LIST);
            }
        }
        return res >= 0;
    }

    private void setUpdateKeyValueMap(Map<String, Object> fieldValueMap, String keyField, Map<String, Object> map) {
        StringBuilder sbFieldValue = new StringBuilder();
        // 业务自定义固定字段
        List<String> fixFieldList = listFixField();
        Iterator<Entry<String, Object>> it = fieldValueMap.entrySet().iterator();
        while (it.hasNext()) {
            Entry<String, Object> entry = it.next();
            String columnName = entry.getKey();
            // 主键禁止更新；业务自定义字段禁止更新
            if (columnName.equalsIgnoreCase(keyField) || fixFieldList.contains(columnName.toLowerCase())) {
                continue;
            }
            // 组装set语句，非主键字段，如果map包含字段可能是filter已经存在，需要修改参数字段
            String paramName = columnName;
            if (map.containsKey(columnName)) {
                paramName = getParamName();
            }
            sbFieldValue.append(getParamExpression(columnName, paramName));
            sbFieldValue.append(DaoConst.COMMA);
            // 参数赋值
            map.put(paramName, entry.getValue());
        }
        String fieldValue = "";
        if (sbFieldValue.length() > 0) {
            fieldValue = sbFieldValue.substring(0, sbFieldValue.length() - 1);
        } else {
            throw new DaoException(DaoConst.FIELD_CAN_NOT_BE_EMPTY);
        }
        map.put("fieldValue", fieldValue);
    }

    private void setUpdateInfo(Table table, Map<String, Object> map) {
        boolean blSyncTable = table.syncTable();
        if (!blSyncTable) {
            setUpdateInfo(map);
        } else {
            String syncDateField = table.syncDateField();
            Integer syncDatePrecision = table.syncDatePrecision().getPrecision();
            setSyncDate(map, syncDateField, syncDatePrecision);
        }
    }
}
